package com.suning.park.web.util.page;

import org.apache.ibatis.builder.xml.dynamic.ForEachSqlNode;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.executor.statement.BaseStatementHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.property.PropertyTokenizer;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.xml.bind.PropertyException;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Properties;

@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })
public class PagePlugin implements Interceptor {

	private Logger logger = LoggerFactory.getLogger(PagePlugin.class);
	private static String dialect = "";
	private static String pageSqlId = "";

	@SuppressWarnings("unchecked")
	public Object intercept(Invocation ivk) {
		logger.debug("分页拦截器start==========>");
		try {
			if (!(ivk.getTarget() instanceof RoutingStatementHandler)) {
				return ivk.proceed();
			}
			RoutingStatementHandler statementHandler = (RoutingStatementHandler) ivk.getTarget();
			BaseStatementHandler delegate = (BaseStatementHandler) ReflectHelper.getValueByFieldName(statementHandler, "delegate");
			MappedStatement mappedStatement = (MappedStatement) ReflectHelper.getValueByFieldName(delegate, "mappedStatement");
			// 不需要分页
			if (!mappedStatement.getId().matches(pageSqlId)) {
				return ivk.proceed();
			}
			BoundSql boundSql = delegate.getBoundSql();
			Object parameterObject = boundSql.getParameterObject();
			if (parameterObject == null) {
				return ivk.proceed();
			} else {
				PageInfo page = null;
				if (parameterObject instanceof PageInfo) {
					page = (PageInfo) parameterObject;
					doPage(ivk, page, boundSql, mappedStatement);
				} else if (parameterObject instanceof Map) {
					Map<String, Object> map = (Map<String, Object>) parameterObject;
					page = (PageInfo) map.get("page");
					if (page == null) {
						return ivk.proceed();
					}
					doPage(ivk, page, boundSql, mappedStatement);
				} else {
					Field pageField = ReflectHelper.getFieldByFieldName(parameterObject, "page");
					if (pageField != null) {
						page = (PageInfo) ReflectHelper.getValueByFieldName(parameterObject, "page");
						if (page == null) {
							return ivk.proceed();
						}
						doPage(ivk, page, boundSql, mappedStatement);
					} else {
						return ivk.proceed();
					}
				}
			}
			return ivk.proceed();
		} catch (Exception e) {
			logger.error("", e);
		} finally {
			logger.debug("<==========分页拦截器end");
		}
		return null;
	}

	/**
	 * 改写分页
	 * 
	 * @param ivk
	 * @param page
	 * @param boundSql
	 * @param mappedStatement
	 * @throws Exception
	 */
	public void doPage(Invocation ivk, PageInfo page, BoundSql boundSql, MappedStatement mappedStatement) throws SQLException, NoSuchFieldException, IllegalAccessException {
		logger.info("--------------分页拦截器开始改写SQL--------------");
		Object parameterObject = boundSql.getParameterObject();
		Connection connection = (Connection) ivk.getArgs()[0];
		String sql = boundSql.getSql();
		String countSql = "select count(0) from (" + sql + ") myCount";
		logger.info("总数sql 语句:" + countSql);
		PreparedStatement countStmt = connection.prepareStatement(countSql);
		BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql, boundSql.getParameterMappings(), parameterObject);
		setParameters(countStmt, mappedStatement, countBS, parameterObject);
		ResultSet rs = countStmt.executeQuery();
		int count = 0;
		if (rs.next()) {
			count = rs.getInt(1);
		}
		rs.close();
		countStmt.close();
		page.setTotalResult(count);
		String pageSql = generatePageSql(sql, page);
		logger.info("page sql:" + pageSql);
		ReflectHelper.setValueByFieldName(boundSql, "sql", pageSql);

		logger.info("--------------分页拦截器改写SQL结束--------------");
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, Object parameterObject) throws SQLException {
		ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
		List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
		if (parameterMappings == null) {
			return;
		}
		Configuration configuration = mappedStatement.getConfiguration();
		TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
		MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);
		for (int i = 0; i < parameterMappings.size(); i++) {
			ParameterMapping parameterMapping = parameterMappings.get(i);
			if (parameterMapping.getMode() != ParameterMode.OUT) {
				Object value;
				String propertyName = parameterMapping.getProperty();
				PropertyTokenizer prop = new PropertyTokenizer(propertyName);
				if (parameterObject == null) {
					value = null;
				} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
					value = parameterObject;
				} else if (boundSql.hasAdditionalParameter(propertyName)) {
					value = boundSql.getAdditionalParameter(propertyName);
				} else if (propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX) && boundSql.hasAdditionalParameter(prop.getName())) {
					value = boundSql.getAdditionalParameter(prop.getName());
					if (value != null) {
						value = configuration.newMetaObject(value).getValue(propertyName.substring(prop.getName().length()));
					}
				} else {
					value = metaObject == null ? null : metaObject.getValue(propertyName);
				}
				TypeHandler typeHandler = parameterMapping.getTypeHandler();
				if (typeHandler == null) {
					throw new ExecutorException("There was no TypeHandler found for parameter " + propertyName + " of statement " + mappedStatement.getId());
				}
				typeHandler.setParameter(ps, i + 1, value, parameterMapping.getJdbcType());
			}
		}
	}

	private String generatePageSql(String sql, PageInfo page) {
		if (page != null && (dialect != null || !dialect.equals(""))) {
			StringBuffer pageSql = new StringBuffer();
			if ("mysql".equals(dialect)) {
				pageSql.append(sql);
				pageSql.append(" limit " + page.getCurrentResult() + "," + page.getShowCount());
			} else if ("oracle".equals(dialect)) {
				pageSql.append("select * from (select tmp_tb.*,ROWNUM row_id from (");
				pageSql.append(sql);
				pageSql.append(")  tmp_tb where ROWNUM<=");
				pageSql.append(page.getCurrentResult() + page.getShowCount());
				pageSql.append(") where row_id>");
				pageSql.append(page.getCurrentResult());
			}
			return pageSql.toString();
		} else {
			return sql;
		}
	}

	public Object plugin(Object arg0) {
		// TODO Auto-generated method stub
		return Plugin.wrap(arg0, this);
	}

	public void setProperties(Properties p) {
		dialect = p.getProperty("dialect");
		if (dialect == null || dialect.equals("")) {
			try {
				throw new PropertyException("dialect property is not found!");
			} catch (PropertyException e) {
				logger.error("", e);
			}
		}
		pageSqlId = p.getProperty("pageSqlId");
		if (dialect == null || dialect.equals("")) {
			try {
				throw new PropertyException("pageSqlId property is not found!");
			} catch (PropertyException e) {
				logger.error("", e);
			}
		}
	}

}